import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.util.Arrays;

import javax.crypto.SecretKey;

import org.apache.commons.codec.binary.Base64;

import com.google.gson.Gson;

public class LotterySecurity{
	
	private RSAEncryptDecrypt RSA = null;
	private AESEncryptDecrypt AES = null;
	
	public LotterySecurity() throws Exception {
		RSA = new RSAEncryptDecrypt();
		AES = new AESEncryptDecrypt();
		
		//RSA.generateKeyRSA("publicKeyAuditor", "privateKeyAuditor");
		//RSA.generateKeyRSA("publicKeyTerminal", "privateKeyTerminal");
		//RSA.generateKeyRSA("publicKeyAuthority", "privateKeyAuthority");
    }
	
	private static byte[] hashing(String message, String shaAlgorithm) throws NoSuchAlgorithmException{
		MessageDigest md = MessageDigest.getInstance(shaAlgorithm);
		md.update(message.getBytes());
		return md.digest();
	}
	
	public String verifyLottery(String lottoryNumber){
		String result = null; 
		String bindSigResult = null;
		BigInteger keyBlindResult = null;
		//BigInteger k = new BigInteger(256, new Random());
		//BigInteger m = new BigInteger(lottoryNumber.getBytes("UTF8"));
        
		
		try {
			
			byte[] m = hashing(lottoryNumber, "SHA-256");
			
			BigInteger an = RSA.LoadPublicKeyRSA("publicKeyAuditor").getModulus();
	        BigInteger ae = RSA.LoadPublicKeyRSA("publicKeyAuditor").getPublicExponent();
			BlindSignature BS = new BlindSignature();
			BigInteger k = BS.generateRandom(an);
			
			keyBlindResult = BS.blindSignature(k, ae, an, new BigInteger(m));
			bindSigResult = keyBlindResult.toString();
			
			Signature sig = Signature.getInstance("SHA256withRSA");
		    sig.initSign(RSA.LoadPrivateKeyRSA("privateKeyTerminal"));
		    sig.update(bindSigResult.getBytes("UTF-8"));
		    
		    byte[] signatureBytes = sig.sign();
		    String message = bindSigResult + "," + new String(Base64.encodeBase64(signatureBytes));
		    
		    byte[][] AfterSpilt = splitByte(message.getBytes("UTF-8"));
			byte[][] cyphered = new byte[AfterSpilt.length][];
		     
		    for(int i = 0; i < AfterSpilt.length; i++){
		    	cyphered[i] = RSA.encryptRSA(AfterSpilt[i], RSA.LoadPublicKeyRSA("publicKeyAuditor"));
		    }
		    
		    AES.generateKeyAES();
		    
		    String cypheredStringGSON = convertArrayOfByteToStringArray(cyphered);

		    String AuditorResponseGSON = AuditorVerify(cypheredStringGSON);
		    
		    byte[][] AuditorResponse = convertStringArrayToArrayOfByte(AuditorResponseGSON);
		    byte[][] decryptMessage = new byte[AuditorResponse.length][];
		    
		    for(int i = 0; i < AuditorResponse.length; i++)
				decryptMessage[i] = RSA.decryptRSA(AuditorResponse[i], RSA.LoadPrivateKeyRSA("privateKeyTerminal"));
		    
		    byte[] decryptMessageAfterMerge = mergeByte(decryptMessage);
		    String decryptMessageAfterMergeForBigInt = new String(decryptMessageAfterMerge);
		    
		    keyBlindResult = BS.unblindSignature(k, new BigInteger(decryptMessageAfterMergeForBigInt), an);
		    
		    if(BS.verifiedSignature(keyBlindResult, ae, an, new BigInteger(m))){
		    	String encryptLx = AES.encryptMessage(lottoryNumber +","+bindSigResult);
		    	
		    	SecretKey SK = AES.getMyPublicKey();
		    	
		    	byte[] AESKey = RSA.encryptRSA(SK.getEncoded(), RSA.LoadPublicKeyRSA("publicKeyAuditor"));
		    	
		    	Signature sig2 = Signature.getInstance("SHA256withRSA");
			    sig2.initSign(RSA.LoadPrivateKeyRSA("privateKeyTerminal"));
			    sig2.update((encryptLx + "," + new String(Base64.encodeBase64(AESKey))).getBytes("UTF-8"));
			    
			    signatureBytes = sig2.sign();
			    
			    message = encryptLx + "," + new String(Base64.encodeBase64(AESKey)) + "," + new String(Base64.encodeBase64(signatureBytes));
			    
			    AfterSpilt = splitByte(message.getBytes());
				cyphered = new byte[AfterSpilt.length][];
			    
			    for(int i = 0; i < AfterSpilt.length; i++){
			    	cyphered[i] = RSA.encryptRSA(AfterSpilt[i], RSA.LoadPublicKeyRSA("publicKeyAuthority"));
			    }
			    
			    cypheredStringGSON = convertArrayOfByteToStringArray(cyphered);

			    String AuthorityResponseGSON = AuthorityVerify(cypheredStringGSON);
			    
			    byte[][] AuthorityResponse = convertStringArrayToArrayOfByte(AuthorityResponseGSON);
			    
			    byte[] MessageAfterMerge = mergeByte(AuthorityResponse);
			    
			    String FullMessage = new String(MessageAfterMerge);
			    String SeQ = FullMessage.split(",")[0];
			    String Lx = FullMessage.split(",")[1];
			    String SignatureL = FullMessage.split(",")[2];
			    
			    byte[] signature = Base64.decodeBase64(SignatureL);
			    
			    Signature sigL = Signature.getInstance("SHA256withRSA");
			    sigL.initVerify(RSA.LoadPublicKeyRSA("publicKeyTerminal"));
			    sigL.update((encryptLx + "," + AESKey).getBytes("UTF-8"));

			    if(sigL.verify(signature)){

			        return SeQ;
			    }
			    else{
			    	return null;
			    }
		    }
			
			
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		
		return result;
	}
	
	public String AuthorityVerify(String cypheredWS) throws Exception{
		
	    byte[] cypheredRecieve[] = convertStringArrayToArrayOfByte(cypheredWS);
		byte[][] decryptMessage = new byte[cypheredRecieve.length][];
		
		for(int i = 0; i < cypheredRecieve.length; i++)
			decryptMessage[i] = RSA.decryptRSA(cypheredRecieve[i], RSA.LoadPrivateKeyRSA("privateKeyAuthority"));
		
		byte[] decryptMessageAfterMerge = mergeByte(decryptMessage);

	    String FullMessage = new String(decryptMessageAfterMerge);
	    String encryptLx = FullMessage.split(",")[0];
	    String AESKey = FullMessage.split(",")[1];
	    String signature = FullMessage.split(",")[2];
	    
	    byte[] Terminalsignature = Base64.decodeBase64(signature);
	    
	    Signature sig = Signature.getInstance("SHA256withRSA");
	    sig.initVerify(RSA.LoadPublicKeyRSA("publicKeyTerminal"));
	    sig.update((encryptLx + "," + AESKey).getBytes("UTF-8"));

	    if(sig.verify(Terminalsignature)){
	    	
	    	LotteryDBManagement LM = new LotteryDBManagement();
	    	String SeQ = LM.PurchaseLottery(encryptLx, AESKey);
		    
	    	sig.initSign(RSA.LoadPrivateKeyRSA("privateKeyAuthority"));
		    sig.update((SeQ + "," + encryptLx).getBytes("UTF-8"));
		    
		    byte[] signatureBytes = sig.sign();
		    
		    String message = SeQ + "," + encryptLx + "," + new String(Base64.encodeBase64(signatureBytes));
	    	
		    byte[][] AfterSpilt = splitByte(message.getBytes());
		    
	        return convertArrayOfByteToStringArray(AfterSpilt);
	    }
	    else{
	    	return null;
	    }
	}
	
	public String AuditorVerify(String cypheredWS) throws Exception{
		
	    byte[] cypheredRecieve[] = convertStringArrayToArrayOfByte(cypheredWS);
		byte[][] decryptMessage = new byte[cypheredRecieve.length][];
		
		BigInteger an = RSA.LoadPublicKeyRSA("publicKeyAuditor").getModulus();
		BigInteger ad = RSA.LoadPrivateKeyRSA("privateKeyAuditor").getPrivateExponent();
        BlindSignature BS = new BlindSignature();
        
		for(int i = 0; i < cypheredRecieve.length; i++)
			decryptMessage[i] = RSA.decryptRSA(cypheredRecieve[i], RSA.LoadPrivateKeyRSA("privateKeyAuditor"));
		
		byte[] decryptMessageAfterMerge = mergeByte(decryptMessage);

	    String FullMessage = new String(decryptMessageAfterMerge);
	    String messageBlind = FullMessage.split(",")[0];
	    String messageSig = FullMessage.split(",")[1];
	    
	    byte[] Terminalsignature = Base64.decodeBase64(messageSig);
	    
	    Signature sig = Signature.getInstance("SHA256withRSA");
	    sig.initVerify(RSA.LoadPublicKeyRSA("publicKeyTerminal"));
	    sig.update(messageBlind.getBytes("UTF-8"));

	    if(sig.verify(Terminalsignature)){
		    
	    	String signatureBytes = BS.signSignature(new BigInteger(messageBlind), ad, an).toString();
	    	
		    byte[][] AfterSpilt = splitByte(signatureBytes.getBytes());
			byte[][] cyphered = new byte[AfterSpilt.length][];
		    
		    for(int i = 0; i < AfterSpilt.length; i++){
		    	cyphered[i] = RSA.encryptRSA(AfterSpilt[i], RSA.LoadPublicKeyRSA("publicKeyTerminal"));
		    }
		    
	    	return convertArrayOfByteToStringArray(cyphered);
	    }
	    else{
	    	return null;
	    }
	}
	
	private String convertArrayOfByteToStringArray(byte[][] message){
		
		Gson gson = new Gson();
	    
		String[] cypheredString = new String[message.length];
	    
	    for(int i = 0; i < message.length; i++){
	    	cypheredString[i] = new String(Base64.encodeBase64(message[i]));
	    }
	    
        return gson.toJson(cypheredString);
	}
	
	private byte[][] convertStringArrayToArrayOfByte(String message){
		
		Gson gson = new Gson();
	    
	    String[] messageGSON = gson.fromJson(message, String[].class);
	    
	    byte[] messageRecieve[] = new byte[messageGSON.length][];
	    
	    for(int i = 0; i < messageGSON.length; i++){
	    	messageRecieve[i] = Base64.decodeBase64(messageGSON[i]);
	    }
	    
	    return messageRecieve;
	}
	
	private byte[][] splitByte(byte[] message){
		
		int length = message.length;
		int arraySize = (length / 200)+1;
		int width = 0;
		byte[][] splitMessage = new byte[arraySize][];
		
		for(int i = 0; i < arraySize; i++){
			splitMessage[i] = Arrays.copyOfRange(message, width, width+200 < length ? width+200 : length);
			width += 200;
		}
		return splitMessage;
	}
	
	private byte[] mergeByte(byte[][] message){

		int currentOffset = 0;
		int size = 0;
		
		for(int i = 0; i< message.length; i++){
			size += message[i].length;
		}
		
		byte[] mergeMessage = new byte[size];
		
	    for(final byte[] currentArray : message){
	        System.arraycopy(
	            currentArray, 0,
	            mergeMessage, currentOffset,
	            currentArray.length
	        );
	        currentOffset += currentArray.length;
	    }
		
		return mergeMessage;
	}
	
}